#include "stdafx.h"
#include "Objects/SelectionGroup.h"
#include "Objects/BaseObject.h"
#include "Brush/SolidBrushObject.h"
#include "Geometry/EdGeometry.h"
#include "IIndexedMesh.h"
#include "OBJExporter1.h"
#include "I3DEngine.h"
#include "Objects/EntityObject.h"
#include "Objects/VoxelObject.h"
#include "Material/Material.h"
#include "ExportObjects.h"

namespace
{


void SetTexture(char * outName, const IRenderShaderResources * pRes, int slot)
{
	SEfResTexture * pTex = pRes->GetTexture(slot);
	if(pTex)
	{
		GetCurrentDirectory(MAX_PATH, outName);
		strcat(outName, "\\");
		strcat(outName, Path::GamePathToFullPath(pTex->m_Name.c_str()));
	}
}


void AddMaterialToModel(COBJExporter & cModel, CBaseObject * obj, CMaterial * pMtl)
{
	if(pMtl)
	{
		CExpMtl mtl;
		strcpy(mtl.m_name, pMtl->GetFullName());
		
		IMaterial * matInfo = pMtl->GetMatInfo();
		IRenderShaderResources * pRes = matInfo->GetShaderItem().m_pShaderResources;
		if(!pRes)
			return;
		mtl.dif  = pRes->GetDiffuseColor();
		mtl.spec = pRes->GetSpecularColor();
		mtl.trans= pRes->GetOpacity();
		mtl.shin = pRes->GetSpecularShininess();

		SetTexture(mtl.m_map_Kd,   pRes, EFTT_DIFFUSE);
		SetTexture(mtl.m_map_Ns,   pRes, EFTT_GLOSS);
		SetTexture(mtl.m_map_d,    pRes, EFTT_OPACITY);
		SetTexture(mtl.m_map_bump, pRes, EFTT_BUMP);
		SetTexture(mtl.m_map_decal,pRes, EFTT_DECAL_OVERLAY);
		SetTexture(mtl.m_map_disp, pRes, EFTT_BUMP_HEIGHT);

		cModel.AddMtl(mtl);
	}
	else
	{
		CExpMtl mtl;
		strcpy(mtl.m_name, obj->GetName());
		cModel.AddMtl(mtl);
	}
}



void AddMeshToModel( COBJExporter & cModel, CBaseObject * obj, IIndexedMesh * pMesh, const Vec3& pivot )
{
	if(!pMesh)
		return;

	cModel.StartNewObject(obj->GetName());

	CMaterial * pMtl = obj->GetRenderMaterial();

	IIndexedMesh::SMeshDescription meshDesc;
	pMesh->GetMeshDescription(meshDesc);

	Matrix34 rotX = Matrix34::CreateRotationX(-3.14159265f*0.5f);
	Matrix34 tm = rotX * obj->GetWorldTM();
	Vec3 RotatedPivot = rotX * pivot;

	for(int v=0; v<meshDesc.m_nVertCount; v++)
	{
		CVector3D vec;
		Vec3 tmp;

		// Currently, " - RotatedPivot" has been disabled to preserve the global position info.
		tmp = (meshDesc.m_pVerts ? meshDesc.m_pVerts[v] : ToVec3(meshDesc.m_pVertsF16[v]));
		tmp = tm.TransformPoint(tmp);// - RotatedPivot;
		vec.fX = tmp.x;
		vec.fY = tmp.y;
		vec.fZ = tmp.z;
		cModel.AddVertex(vec);

		tmp = tm.TransformVector(meshDesc.m_pNorms[v]);
		tmp = tmp.normalize();
		vec.fX = tmp.x;
		vec.fY = tmp.y;
		vec.fZ = tmp.z;
		cModel.AddNormal(vec);
	}

	for(int v=0; v<meshDesc.m_nCoorCount; v++)
	{
		CTexCoord2D tc;
		tc.fU = meshDesc.m_pTexCoord[v].s;
		tc.fV = meshDesc.m_pTexCoord[v].t;
		cModel.AddTexCoord(tc);
	}

	if(pMtl && pMtl->IsMultiSubMaterial() && pMesh->GetSubSetCount()
		&& !(pMesh->GetSubSetCount()==1 && pMesh->GetSubSet(0).nNumIndices==0))
	{
		for( int i=0; i<pMesh->GetSubSetCount(); i++)
		{
			cModel.StartNewSubMesh();
			const SMeshSubset & sms = pMesh->GetSubSet(i);

			int nTris = sms.nNumIndices/3;
			for(int f=0; f<nTris; f++)
			{
				CFace face;
				face.iVertexIndices[0]=meshDesc.m_pIndices[sms.nFirstIndexId + f*3+0];
				face.iVertexIndices[1]=meshDesc.m_pIndices[sms.nFirstIndexId + f*3+1];
				face.iVertexIndices[2]=meshDesc.m_pIndices[sms.nFirstIndexId + f*3+2];

				face.iTexCoordIndices[0]=meshDesc.m_pIndices[sms.nFirstIndexId + f*3+0];
				face.iTexCoordIndices[1]=meshDesc.m_pIndices[sms.nFirstIndexId + f*3+1];
				face.iTexCoordIndices[2]=meshDesc.m_pIndices[sms.nFirstIndexId + f*3+2];

				cModel.AddFace(face);
			}

			CMaterial * pSubMtl = 0;
			if(sms.nMatID < pMtl->GetSubMaterialCount())
				pSubMtl = pMtl->GetSubMaterial(sms.nMatID);
			AddMaterialToModel(cModel, obj, pSubMtl);
		}
	}
	else
	{
		cModel.StartNewSubMesh();
		if (meshDesc.m_nFaceCount == 0 && meshDesc.m_nIndexCount != 0 && meshDesc.m_pIndices != 0)
		{
			int nTris = meshDesc.m_nIndexCount/3;
			for(int f=0; f<nTris; f++)
			{
				CFace face;
				face.iVertexIndices[0]=meshDesc.m_pIndices[f*3+0];
				face.iVertexIndices[1]=meshDesc.m_pIndices[f*3+1];
				face.iVertexIndices[2]=meshDesc.m_pIndices[f*3+2];

				face.iTexCoordIndices[0]=meshDesc.m_pIndices[f*3+0];
				face.iTexCoordIndices[1]=meshDesc.m_pIndices[f*3+1];
				face.iTexCoordIndices[2]=meshDesc.m_pIndices[f*3+2];

				cModel.AddFace(face);
			}
		}
		else
		{
			for(int f=0; f<meshDesc.m_nFaceCount; f++)
			{
				CFace face;
				face.iVertexIndices[0]=meshDesc.m_pFaces[f].v[0];
				face.iVertexIndices[1]=meshDesc.m_pFaces[f].v[1];
				face.iVertexIndices[2]=meshDesc.m_pFaces[f].v[2];

				face.iTexCoordIndices[0]=meshDesc.m_pFaces[f].t[0];
				face.iTexCoordIndices[1]=meshDesc.m_pFaces[f].t[1];
				face.iTexCoordIndices[2]=meshDesc.m_pFaces[f].t[2];

				cModel.AddFace(face);
			}
		}

		AddMaterialToModel(cModel, obj, pMtl);
	}
}

IIndexedMesh* GetMesh(CBaseObject* obj)
{
	CEdGeometry * pEdGeom = obj->GetGeometry();
	IIndexedMesh* pMesh = NULL;

	if(obj->GetType() == OBJTYPE_SOLID)
	{
		CSolidBrushObject *sobj = (CSolidBrushObject*)obj;
		pMesh = GetIEditor()->Get3DEngine()->CreateIndexedMesh();
		// Using not optimized mesh

		SBrush* pBrush = NULL;
		if (sobj->GetBrush(pBrush))
			pBrush->GenerateIndexMesh(pMesh);
	}
	else if (obj->GetType() == OBJTYPE_VOXEL)
	{
		CVoxelObject *pCVO = (CVoxelObject*)obj;
		IVoxelObject *pIVO = (IVoxelObject*)pCVO->GetEngineNode();
		if (pIVO)
			pMesh = pIVO->CreateIndexedMesh();
	}

	if(!pMesh && pEdGeom)
		pMesh = pEdGeom->GetIndexedMesh();

	if(!pMesh && obj->GetType() == OBJTYPE_ENTITY)
	{
		CEntityObject * pEnt = (CEntityObject *)obj;
		IEntity * pIEnt = pEnt->GetIEntity();

		if(pIEnt)
		{
			IStatObj *pStatObj = pIEnt->GetStatObj(0);
			if(pStatObj)
			{
				if (pStatObj && pStatObj->GetParentObject())
					pStatObj = pStatObj->GetParentObject();

				if(pStatObj->GetSubObjectCount())
				{
					for(int i = 0; i<pStatObj->GetSubObjectCount(); i++)
					{
						IStatObj::SSubObject * pSubObj = pStatObj->GetSubObject(i);
						if(pSubObj && pSubObj->nType==STATIC_SUB_OBJECT_MESH && pSubObj->pStatObj)
						{
							pMesh = pSubObj->pStatObj->GetIndexedMesh(true);
							if(pMesh)
								break;
						}
					}
				}

				if(!pMesh)
				{
					pMesh = pStatObj->GetIndexedMesh(true);
				}
			}
		}
	}	
	return pMesh;
}

bool ExportObject( COBJExporter & cModel, CBaseObject *obj, const Vec3& pivot )
{
	if(obj->GetType() == OBJTYPE_GROUP)
	{
		for(int i=0; i<obj->GetChildCount(); i++)
			if(!ExportObject( cModel, obj->GetChild(i), pivot ))
				return false;
		return true;
	}

	IIndexedMesh* pMesh = GetMesh(obj);

	if(!pMesh)
		return false;

	AddMeshToModel( cModel, obj, pMesh, pivot );
	return true;
}

} // namespace

bool CExportObjects::CheckIsExportable(CSelectionGroup *sel)
{
	if(!sel)
		return false;

	std::vector<string> InvalidObjs;
	for(int i=0; i<sel->GetCount(); ++i)
	{
		CBaseObject* obj = sel->GetObject(i);
		if(obj->GetType() == OBJTYPE_GROUP)
		{
			for(int j=0; j<obj->GetChildCount(); ++j)
			{
				if(NULL == GetMesh(obj->GetChild(j)))
					InvalidObjs.push_back( obj->GetChild(j)->GetName().GetString() );
			}

			return true;
		}
		else
		{
			if(NULL == GetMesh(obj))
				InvalidObjs.push_back( obj->GetName().GetString() );
		}
	}
	
	if(InvalidObjs.empty())
		return true;

	string res = InvalidObjs[0];
	for(int i=1; i<(int)InvalidObjs.size(); ++i)
	{
		res += ", ";
		res += InvalidObjs[i];
	}

	if(res.size() > 500)
	{
		res = res.substr(0, 499);
		res += "(and more ...)";
	}

	CString strOut;
	strOut.Format("Failed to get mesh from object %s, we currently only support CGF models and non-empty voxels!", res.c_str());
	CryLog(strOut.GetString());
	AfxMessageBox(strOut.GetString());

	return false;
}

bool CExportObjects::Export( const char *pszFileName )
{
	COBJExporter cModel;
	CSelectionGroup *sel = GetIEditor()->GetSelection();

	Vec3 pivot(0,0,0);

	if( sel->GetCount() == 1 )
	{
		CBaseObject* object = sel->GetObject(0);
		pivot = object->GetWorldTM().GetTranslation();
	}
	else
	{
		AABB worldAABB;
		worldAABB.Reset();
		for(int i=0; i<sel->GetCount(); ++i)
		{
			CBaseObject* object = sel->GetObject(i);

			AABB aabb;
			object->GetBoundBox(aabb);
			const Matrix34& worldTM(object->GetWorldTM());

			worldAABB.Add(worldTM.TransformPoint(aabb.min));
			worldAABB.Add(worldTM.TransformPoint(aabb.max));
		}
		pivot = worldAABB.GetCenter();
	}

	for(int i=0; i<sel->GetCount(); i++)
	{
		if(!ExportObject( cModel, sel->GetObject(i), pivot ))
			return false;
	}

	// Write the model into the file
	if (!cModel.WriteOBJ(pszFileName))
	{
		CLogFile::WriteLine("Error while exporting part of the heightmap as OBJ model !");
		AfxMessageBox("Crititcal error during exporting !");
		return false;
	}

	return true;
}